package com.atguigu.tingshu.user.service.impl;

import java.util.Date;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.common.constant.PublicConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.google.common.collect.Maps;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.user.mapper.UserInfoMapper;
import com.atguigu.tingshu.user.service.UserInfoService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Autowired
    private WxMaService wxMaService; //微信服务端通信的一个客户端

    @Autowired
    private RsaSigner rsaSigner;

    @Autowired
    private StringRedisTemplate redisTemplate;


    @Autowired

    private RabbitService rabbitService;


    @Override
    public Map<String, Object> wxLogin(String code) {

        String openId = "";


        // 1.校验code码是否存在
        if (StringUtils.isEmpty(code)) {
            throw new GuiguException(201, "code码不存在，登录失败");
        }

        // 2.获取微信登录的客户端
        WxMaUserService userService = wxMaService.getUserService();
        try {
            WxMaJscode2SessionResult sessionInfo = userService.getSessionInfo(code);
            openId = sessionInfo.getOpenid();// 用户身份的唯一标识
            if (StringUtils.isEmpty(openId)) {
                throw new GuiguException(201, "openId不存在,登录失败");
            }
        } catch (WxErrorException e) {
            throw new GuiguException(201, "微信内部处理异常，登录失败");
        }


        String refreshTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + "refresh:" + openId;
        String fromRedisJwt = redisTemplate.opsForValue().get(refreshTokenKey);
        if (!StringUtils.isEmpty(fromRedisJwt)) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("token", fromRedisJwt);
            map.put("refreshToken", fromRedisJwt);
            return map;
        }


        // 3.查询数据库（用户是否存在）
        // 3.1 创建一个条件对象
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        // 3.2 给条件对象加条件
        queryWrapper.eq(UserInfo::getWxOpenId, openId);
        UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
        // 3.3 用户信息不存在（注册用户信息）
        if (null == userInfo) {
            // 注册
            userInfo = new UserInfo();
            userInfo.setWxOpenId(openId);
            userInfo.setNickname("昵称-hzk-【" + System.currentTimeMillis() + "】");
            userInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg"); //初始化一个用户的头像
            userInfo.setIsVip(0);
            userInfo.setVipExpireTime(new Date());
            userInfo.setGender(0);
            userInfo.setIntro("简介");

            int insert = userInfoMapper.insert(userInfo);
            log.info("注册用户信息：{}", insert > 0 ? "success" : "fail");
            // 初始化这个用户的账户信息(异步发送消息)
            rabbitService.sendMessage(MqConst.EXCHANGE_ACCOUNT, MqConst.ROUTING_USER_REGISTER, userInfo.getId().toString());
        }
        // 3.4 自定义登录信息
        // 思路：1、只用uuid 优点：唯一 缺点：身份信息不具有代表性
        // 思路：2、只用jwt.json web token("xxxxxxxx")  优点：唯一 且还具有身份的唯一标识
//        String token = UUID.randomUUID().toString().replaceAll("-", "");
        String jsonWebToken = getJsonWebToken(openId, userInfo.getId());


        // 4.将jwt令牌存储到分布式组件Redis中
        // redis的常见数据类型：五种（String  Set  Zset  List  Hash(大key,value(小key,value))）
        // 登录信息要在redis中缓存多久。
        // 缓存时间短一些：缺点：频繁登录（用户体验非常差） 优点：安全
        // 缓存时间长一些：缺点：不安全 优点：不需要频繁登录
        // 双令牌机制：token有两个（AccessToken/RefreshToken），且这两个时间不同，一个时间长一些(RefreshToken) 一个时间短一些(AccessToken)
        // AccessToken:作用：用它可以进行对受保护资源进行访问。
        // RefreshToken:作用：主要用来刷新AccessToken.


        String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;

//        redisTemplate.opsForValue().set(accessTokenKey, jsonWebToken, 10, TimeUnit.MINUTES);  // 线上时间
        redisTemplate.opsForValue().set(accessTokenKey, jsonWebToken, 15, TimeUnit.DAYS);
        redisTemplate.opsForValue().set(refreshTokenKey, jsonWebToken, 30, TimeUnit.DAYS);
        // 5. 将自定义登录信息返回给前端
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", jsonWebToken);   // token:前端用的key
        map.put("refreshToken", jsonWebToken);
        // session作用域下（自己放 自己取） 但是别人获取不到
        return map;
    }


    private String getJsonWebToken(String openId, Long userId) {
        JSONObject payloadMap = new JSONObject();
        payloadMap.put("openId", openId);
        payloadMap.put("userId", userId);
        // 3.5 得到jwt对象
        Jwt jwt = JwtHelper.encode(payloadMap.toString(), rsaSigner);
        // 3.6 获取jwt对象的token
        String jsonWebToken = jwt.getEncoded();
        return jsonWebToken;
    }

    @Override
    public Map<String, Object> refreshToken() {

        // 1.获取请求对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        // 2.获取请求对象
        HttpServletRequest request = requestAttributes.getRequest();

        // 3.获取请求头的token值
        String refreshToken = request.getHeader("token");

        // 4.用jwt验签和解析载荷数据
        Jwt jwt = JwtHelper.decodeAndVerify(refreshToken, new RsaVerifier(PublicConstant.PUBLIC_KEY));

        String claims = jwt.getClaims();
        Map map = JSONObject.parseObject(claims, Map.class);
        String openId = (String) map.get("openId");
        Long userId = (Long) map.get("userId");

        // 5.判断refreshToken是否过期

        // 如果refreshToken过期 用户要登录，提示失败要登录的信息
        String refreshTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + "refresh:" + openId;
        String dataFromRedis = redisTemplate.opsForValue().get(refreshTokenKey);
        if (!StringUtils.isEmpty(dataFromRedis)) {
            // 5.1 如果refreshToken没有过期 才生成一个新令牌
            String jsonWebToken = getJsonWebToken(openId, userId);
            String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;
//            redisTemplate.opsForValue().set(accessTokenKey, jsonWebToken, 10, TimeUnit.MINUTES);
            redisTemplate.opsForValue().set(accessTokenKey, jsonWebToken, 15, TimeUnit.DAYS);
            redisTemplate.opsForValue().set(refreshTokenKey, jsonWebToken, 30, TimeUnit.DAYS);

            Map<String, Object> map1 = new HashMap<>();
            map1.put("token", jsonWebToken);
            map1.put("refreshToken", jsonWebToken);
            return map1;
        }

        // 5.2 如果refreshToken过期 去登录
        throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);

    }

    @Override
    public UserInfoVo getUserInfo() {

        // 1.从认证中心的ThreadLocal中获取用户id
        Long userId = AuthContextHolder.getUserId();

        // 2.查询用户表
        UserInfo userInfo = userInfoMapper.selectById(userId);
        if (null == userInfo) {
            throw new GuiguException(201, "用户信息不存在");
        }

        // 3.将UserInfo转成UserInfoVo对象返回
        UserInfoVo userInfoVo = new UserInfoVo();
        BeanUtils.copyProperties(userInfo, userInfoVo); // 两个对象的属性名 两个对象的属性类型是否一致

        return userInfoVo;
    }

    @Override
    public void updateUser(UserInfoVo userInfoVo) {

        // 1.获取老的用户信息
        Long userId = AuthContextHolder.getUserId();

        UserInfo userInfo = userInfoMapper.selectById(userId);
        if(null==userInfo){
            throw new GuiguException(201,"用户信息不存在");
        }
        userInfo.setNickname(userInfoVo.getNickname());
        userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
        userInfoMapper.updateById(userInfo);

    }


}
